Spring Validation的用法

概念

    1. JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
      这包括方便地支持将JSR-303或JSR-349bean验证提供程序引导为springbean默认情况下,如果类路径 上存在Bean验证(比如, Hibernate Validator),LocalValidatorFactoryBean将会被注册为全局验证器,来供在controller层的方法参数上加入@Valid或者@Validated 注解的参数使用。
      在java配置的方式下,你可以通过以下方式来个性化校验器
1
2
3
4
5
6
7
8
9
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

@Override
public Validator getValidator() {
// ...
}
}

您也可以通过以下方式注入自定义的validator

1
2
3
4
5
6
7
8
9
@Controller
public class MyController {

@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new FooValidator());

}
}
    1. Spring Validation
      spring3对其校验带来了一些提升支持,首先对JSR-303 Bean Validation API 进行了全面的支持,第二点使用编程的方式,Spring的DataBinder能够验证那些绑定了的验证对象。第三点 Spring MVC支持声明性地验证@Controller的输入。
  • 2.1 JSR-303 Bean Validation API摘要
    JSR-303标准化了Java平台的验证约束声明和元数据。通过使用该标准化的API,您可以对域模型的属性进行申明式的验证,在运行时作用于这些域。您可以使用许多内置约束,同样您也可以自定义自己的验证。
    考虑下面的示例,它显示了一个具有两个属性的简单PersonForm模型:

1
2
3
4
public class PersonForm {
private String name;
private int age;
}

JSR-303允许您针对此类属性定义声明性验证约束,如下例所示:

1
2
3
4
5
6
7
8
9
public class PersonForm {

@NotNull
@Size(max=64)
private String name;

@Min(0)
private int age;
}

当JSR-303验证器验证该类的实例时,将强制执行这些约束。

  • 2.2 配置一个bean的验证实现者
    spring 对 Bean Validation API提供了全面的支持。
    这包括方便地支持将JSR-303或JSR-349bean验证提供程序引导为springbean
    它允许你通过注入一个javax.validation.ValidatorFactory或者javax.validation.Validator到您校验需要的地方

JSR303定义的校验类型

Spring Validation验证框架对参数的验证机制提供了@Validated(Spring’s JSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR-303规范),配合BindingResult可以直接提供参数验证结果。
JSR303定义的校验类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.

Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false

长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) 验证注解的元素值长度在min和max区间内

日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则

数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。

@Range(min=, max=) 验证注解的元素值在最小值和最大值之间
@Range(min=10000,max=50000,message="range.bean.wage")
private BigDecimal wage;

@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)

在检验入参是否符合规范时,使用@Validated或者@Valid在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同:

1.    分组:@Validated 支持分组,不展开讲了,@Valid不支持分组

2.    注解地方
@Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上
两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能。
搞个表格直观一点:

1
2
3
4
类型/是否可以 | 使用在类型上 | 使用在方法上 | 使用在方法参数上 | 构造函数 | 成员属性(字段)
--------- | ------------- | ------------- | ------------- | ------------- | -------------
@Validated | 是 | 是 | 是 | 待确认 | 否
@Valid | 否 | 是 | 是 | 是 | 是
  1. 嵌套验证:在比较两者嵌套验证时,先说明下什么叫做嵌套验证。
    比如我们现在有个实体叫做Item:
1
2
3
4
5
6
7
8
9
10
public class Item {

@NotNull(message = "id不能为空")
@Min(value = 1, message = "id必须为正整数")
private Long id;

@NotNull(message = "props不能为空")
@Size(min = 1, message = "至少要有一个属性")
private List<Prop> props;
}

Item带有很多属性,属性里面有:pid、vid、pidName和vidName,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Prop {

@NotNull(message = "pid不能为空")
@Min(value = 1, message = "pid必须为正整数")
private Long pid;

@NotNull(message = "vid不能为空")
@Min(value = 1, message = "vid必须为正整数")
private Long vid;

@NotBlank(message = "pidName不能为空")
private String pidName;

@NotBlank(message = "vidName不能为空")
private String vidName;
}

属性这个实体也有自己的验证机制,比如pid和vid不能为空,pidName和vidName不能为空等。
现在我们有个ItemController接受一个Item的入参,想要对Item进行验证,如下所示:

1
2
3
4
5
6
7
8
@RestController
public class ItemController {

@RequestMapping("/item/add")
public void addItem(@Validated Item item, BindingResult bindingResult) {
doSomething();
}
}

在上图中,如果Item实体的props属性不额外加注释,只有@NotNull和@Size,无论入参采用@Validated还是@Valid验证,Spring Validation框架只会对Item的id和props做非空和数量验证,不会对props字段里的Prop实体进行字段验证,也就是@Validated和@Valid加在方法参数前,都不会自动对参数进行嵌套验证。也就是说如果传的List中有Prop的pid为空或者是负数,入参验证不会检测出来。
为了能够进行嵌套验证,必须手动在Item实体的props字段上明确指出这个字段里面的实体也要进行验证。由于@Validated不能用在成员属性(字段)上,但是@Valid能加在成员属性(字段)上,而且@Valid类注解上也说明了它支持嵌套验证功能,那么我们能够推断出:@Valid加在方法参数时并不能够自动进行嵌套验证,而是用在需要嵌套验证类的相应字段上,来配合方法参数上@Validated或@Valid来进行嵌套验证。
我们修改Item类如下所示:

1
2
3
4
5
6
7
8
9
10
11
public class Item {

@NotNull(message = "id不能为空")
@Min(value = 1, message = "id必须为正整数")
private Long id;

@Valid // 嵌套验证必须用@Valid
@NotNull(message = "props不能为空")
@Size(min = 1, message = "props至少要有一个自定义属性")
private List<Prop> props;
}

然后我们在ItemController的addItem函数上再使用@Validated或者@Valid,就能对Item的入参进行嵌套验证。此时Item里面的props如果含有Prop的相应字段为空的情况,Spring Validation框架就会检测出来,bindingResult就会记录相应的错误。
总结一下@Validated和@Valid在嵌套验证功能上的区别:
@Validated:用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。
@Valid:用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。

异常处理

对于每个注解抛什么异常需要进行总结和归纳,便于在统一异常处理的地方做处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestController
@Validated
public class TestController {

@GetMapping("/NotEmpty")
public String notEmpty(@NotEmpty String me){
return "NotEmpty";
}

@GetMapping("/NotNull")
public String notNull(@NotNull String me){
return "NotNull";
}

@GetMapping("/NotBlank")
public String notBlank(@NotBlank String me){
return "NotBlank";
}

@GetMapping("/Null")
public String snull(@Null String me){
return "Null";
}
}

这三个注解在不传“me”这个参数的时候,都抛ConstraintViolationException异常,该异常 继承于ValidationException 继承于RuntimeException
注意:正对于这种非嵌套类型的校验,必须要在类上使用@Validated注解,只能用这个,原因后面分析下(T0D0) .不能用其他的,也不能在其他地方加。

对于嵌套类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//嵌套类型
@PostMapping("/item/add0")
public void addItem0(@Validated Item item) {
System.out.println("dosmt0");
}

@PostMapping("/item/add1")
public void addItem1(@Validated Item item, BindingResult bindingResult) {
System.out.println("dosmt1"+bindingResult);
}

@PostMapping("/item/add2")
public void addItem2(@Valid Item item, BindingResult bindingResult) {
System.out.println("dosmt2"+bindingResult);
}

会抛BindException,如果我们没有统一的ExceptionHandler的话,会走spring的DefaultHandlerExceptionResolver,然后将结果绑定到DefaultMessageSourceResolvable里的BindingResult里
DefaultHandlerExceptionResolver的优先级是最低的,所以一般会使用其他优先级高的处理器
我们仅用默认优先级即可,当时messageResolver还是会用默认的。

如果使用“/item/add1” 带了BindingResult的话则不会抛异常,为空的结果直接绑定到这个结果对象里

但是如果使用“/item/add2”中的@Valid 则会抛一个ConstraintViolationException的异常,这个使用的时候要小心,具体原因后面分析。(T0D0)
先建议大家用@Valid 这个毕竟是java正统的JSR303注解,除非一些不可用的地方使用spring的

分享到